package observer;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;

import state.Globals;

import messages.BaseMessage;
import messages.GeneralAck;
import messages.MessageFactory;
import messages.MessageType;
import messages.registration.ObserverRegistrationMessage;

public class ObserverThread extends Thread {
    
    private InetAddress remoteAddr;
    private int remotePort;
    private Socket remoteSock;
    
    private ArrayList<ObserverListener> listeners = new ArrayList<ObserverListener>();
    
    public ObserverThread(InetAddress remoteAddr, int remotePort)
    {
        this.remoteAddr = remoteAddr;
        this.remotePort = remotePort;
    }
    
    public boolean init()
    {
        try
        {
            remoteSock = new Socket(remoteAddr, remotePort);
        }
        catch (IOException ex)
        {
            Globals.getLogger().error("Could not open a socket to the server", ex);
            return false;
        }
        
        if (Globals.isDebug())
        {
        	Globals.getLogger().log("ObserverThread sending out registration message: " + getRegistrationMessage());
        }
        
        try
        {
            remoteSock.getOutputStream().write(getRegistrationMessage().toByteArray());
        }
        catch (IOException e)
        {
            Globals.getLogger().error("Could not write the registration message!", e);
            return false;
        }
        
        BaseMessage message = null;
        try
        {
            message = MessageFactory.readMessage(remoteSock);
        }
        catch (IOException e)
        {
            Globals.getLogger().error("Error receiving the ack message", e);
            return false;
        }
        
        if (message.getMessageType() == MessageType.GeneralAck)
        {
            GeneralAck ack = (GeneralAck) message;
            
            return isProperRegistrationAck(ack.getAckMessageType());
        }
        
        return false;
    }
    
    @Override
    public void run()
    {
        if (!init())
        {
            if (remoteSock != null)
            {
                try
                {
                    remoteSock.close();
                }
                catch (IOException e)
                {
                    Globals.getLogger().error("Could not close socket after failed init", e);
                }
            }
            Globals.getLogger().log("Could not initiate a connection, shutting down thread", this.getClass().getName(), "run");
            return;
        }
        
        if (!onStart())
        {
        	try
        	{
        		remoteSock.close();
        	}
        	catch(IOException e)
        	{
        	    Globals.getLogger().error("Could not close socket after failed onStart", e);
        	}
        	Globals.getLogger().log("onStart failed, shutting down thread", this.getClass().getName(), "run");
        }
        
        while (!remoteSock.isClosed())
        {
            BaseMessage message = null;
            try
            {
                message = MessageFactory.readMessage(remoteSock, 2);
            }
            catch (SocketTimeoutException e)
            {
            	//don't care!
            	continue;
            }
            catch (IOException e)
            {
                Globals.getLogger().error("Error reading remote Message!", e);
            }
            
            if (message == null)
            {
                try
                {
                    remoteSock.close();
                }
                catch (IOException e)
                {
                    Globals.getLogger().error("Error closing socket after failed read!", e);
                }
            }
            
            if (message != null)
            {
                for (ObserverListener listener : listeners)
                {
                    listener.handleMessage(message);
                }
            }
        }
        
        try
        {
            if (! remoteSock.isClosed())
            {
                remoteSock.close();
            }
        }
        catch (IOException e)
        {
            Globals.getLogger().error("Error closing socket while shutting down thread!", e);
        }
    }
    
    protected boolean onStart(){
    	return true;
    }
    
    protected BaseMessage getRegistrationMessage()
    {
        return new ObserverRegistrationMessage();
    }
    
    protected boolean isProperRegistrationAck(MessageType type)
    {
        return type == MessageType.ObserverRegistrationMessage;
    }
    
    public void addListener(ObserverListener listener)
    {
        this.listeners.add(listener);
    }
    
    public boolean removeListener(ObserverListener listener)
    {
        return this.listeners.remove(listener);
    }
    
    public Socket getRemoteSocket()
    {
        return remoteSock;
    }
    
    public void shutdownThread()
    {
        try
        {
            remoteSock.close();
        }
        catch (IOException e)
        {
            Globals.getLogger().error("Error encountered while closing the socket on remote shutdown", e);
        }
        
        try
        {
            join(1000);
        }
        catch (InterruptedException e)
        {
            // NOOP
        	e.printStackTrace();
        }
    }
    
}
